namespace SubhadraSolutions.Sharp.Logging.LogSubscribers.FileSystem
{
    using SubhadraSolutions.Sharp.Utils;
    using SubhadraSolutions.Sharp.Utils.Algorithms;
    using SubhadraSolutions.Sharp.Utils.Collections.Generic;
    using SubhadraSolutions.Sharp.Utils.IO;
    using SubhadraSolutions.Sharp.Utils.Threading;
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Globalization;
    using System.IO;
    using System.Threading;
    using System.Threading.Tasks;

    public abstract class AbstractFileLogSubscriberCore : AbstractLogSubscriber
    {
        #region Fields

        protected int newFileSizeThreshold = 5;
        protected bool onlyAppendMode = true;
        protected long _logItemIndex = 0;
        protected EventWaitHandle _fileAccessWaitHandle = null;
        protected FileInfo _writingFileInfo = null;

        private string _bareFileFormat;
        private string _fileFormat;
        private bool _isWaitHandleOwner;
        private int _maxFileCount = 100;
        private DateTime _nextRollupTime;
        private SizedQueue<string> _previousFileNamesQueue;
        private string _rollingTime;
        private List<Time> _rollingTimes;

        #endregion Fields

        #region Constructors

        protected AbstractFileLogSubscriberCore()
        {
            AutoFlush = false;
            MaxFileCount = 100;
            DoRollingAtApplicationStart = true;
            ZipOption = ZipOptions.None;
        }

        ~AbstractFileLogSubscriberCore()
        {
            Dispose(false);
        }

        #endregion Constructors

        #region Properties

        public bool AutoFlush
        {
            get;
            set;
        }

        public bool DoRollingAtApplicationStart
        {
            get;
            set;
        }

        public string FileName
        {
            get;
            set;
        }

        public int MaxFileCount
        {
            get { return _maxFileCount; }
            set { _maxFileCount = value <= 0 ? 100 : value; }
        }

        public long MaxFileSize
        {
            get;
            set;
        }

        public string ProcessInstanceName
        {
            get;
            set;
        }

        public string RollingTime
        {
            get { return _rollingTime; }
            set
            {
                _rollingTime = value;
                if (value == null)
                {
                    _rollingTimes = null;
                }
                else
                {
                    string[] splits = value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
                    if (splits.Length > 0)
                    {
                        _rollingTimes = new List<Time>();
                    }
                    try
                    {
                        foreach (string split in splits)
                        {
                            if (!string.IsNullOrWhiteSpace(split))
                            {
                                Time ts = Time.Parse(split);
                                _rollingTimes.Add(ts);
                            }
                        }
                        if (_rollingTimes.Count == 0)
                        {
                            _rollingTimes = null;
                        }
                        else
                        {
                            _rollingTimes.Sort(TimeComparer.Instance);
                        }
                    }
                    catch
                    {
                        _rollingTimes = null;
                        throw;
                    }
                }
            }
        }

        public ZipOptions ZipOption
        {
            get;
            set;
        }

        #endregion Properties

        #region Methods

        protected abstract void createNewFile();

        protected override void Dispose(bool disposing)
        {
            base.Dispose(disposing);
            _fileAccessWaitHandle.Set();
            _fileAccessWaitHandle.Dispose();
        }

        protected void initializeFileSettings()
        {
            if (_rollingTimes != null && _rollingTimes.Count > 0)
            {
                _nextRollupTime = determineNextRollupTime();
            }

            string writingFileName = IOHelper.ResolvePath(FileName,
                LoggingSettings.Instance.LogsFolderDefaultPath);
            _writingFileInfo = new FileInfo(writingFileName);
            _previousFileNamesQueue = new SizedQueue<string>(_maxFileCount);
            _previousFileNamesQueue.OnQueueFull += _previousFileNamesQueue_OnQueueFull;

            int index = writingFileName.LastIndexOf('.');
            if (index > -1)
            {
                _fileFormat = writingFileName.Substring(0, index) + ".{0}" +
                              writingFileName.Substring(index);
            }
            else
            {
                _fileFormat = writingFileName + ".{0}";
            }
            _bareFileFormat = _fileFormat.Substring(_writingFileInfo.DirectoryName.Length + 1);
            string waitHandleName = writingFileName.Replace('.', '_');
            waitHandleName.MutableReplace('\\', '_');
            waitHandleName.MutableReplace(' ', '_');
            waitHandleName.MutableToUpper();
            _fileAccessWaitHandle = ThreadingHelper.GetOrCreateNamedWaitHandle(waitHandleName,
                EventResetMode.AutoReset,
                out _isWaitHandleOwner);
            try
            {
                _fileAccessWaitHandle.WaitOne();
                string directoryName = Path.GetDirectoryName(writingFileName);
                if (!Directory.Exists(directoryName))
                {
                    Directory.CreateDirectory(directoryName);
                }
                else
                {
                    loadOldFiles();
                    if (_isWaitHandleOwner)
                    {
                        if (DoRollingAtApplicationStart)
                        {
                            tryRollingWritingFile(true, true);
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex);
            }
            finally
            {
                _fileAccessWaitHandle.Set();
            }
        }

        protected override void initializeProtected()
        {
            base.initializeProtected();
            initializeFileSettings();
        }

        protected override void publishLogs(IEnumerable<LogStoreItem> e)
        {
            try
            {
                _fileAccessWaitHandle.WaitOne();
                doRollupIfRequired();

                writeInfoFile(e);
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex);
            }
            finally
            {
                _fileAccessWaitHandle.Set();
            }
        }

        protected abstract void writeInfoFile(IEnumerable<LogStoreItem> e);

        protected virtual void zipFile(string newFileName)
        {
            try
            {
                ZipHelper.ZipFile(newFileName);
                if (ZipOption == ZipOptions.DeleteOriginal)
                {
                    IOHelper.DeleteFile(newFileName, false);
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex);
            }
        }

        private DateTime determineNextRollupTime()
        {
            DateTime now = GlobalSettings.Instance.CurrentDateTime;
            var rollupTime = new DateTime();
            int i = 0;
            for (; i < _rollingTimes.Count; i++)
            {
                rollupTime = new DateTime(now.Year, now.Month, now.Day, _rollingTimes[i].Hour,
                    _rollingTimes[i].Minute,
                    _rollingTimes[i].Second);
                if (rollupTime >= now)
                {
                    break;
                }
            }
            if (i >= _rollingTimes.Count)
            {
                rollupTime = new DateTime(now.Year, now.Month, now.Day, _rollingTimes[0].Hour,
                    _rollingTimes[0].Minute,
                    _rollingTimes[0].Second);
                rollupTime = rollupTime.AddDays(1);
            }
            return rollupTime;
        }

        private void doRollupIfRequired()
        {
            bool isBackedUp = false;
            _writingFileInfo.Refresh();
            if (_writingFileInfo.Exists)
            {
                if (MaxFileSize > 0)
                {
                    if (_writingFileInfo.Length >= MaxFileSize)
                    {
                        tryRollingWritingFile(false, false);
                    }
                    isBackedUp = true;
                }
                if (!isBackedUp && RollingTime != null)
                {
                    if (GlobalSettings.Instance.CurrentDateTime >= _nextRollupTime)
                    {
                        DateTime createTime = GlobalSettings.Instance.UseUtcTime
                            ? _writingFileInfo.CreationTimeUtc
                            : _writingFileInfo.CreationTime;
                        if (createTime < _nextRollupTime)
                        {
                            if (tryRollingWritingFile(false, false))
                            {
                                _nextRollupTime = determineNextRollupTime();
                            }
                        }
                        else
                        {
                            _nextRollupTime = determineNextRollupTime();
                        }
                    }
                }
            }
        }

        private void loadOldFiles()
        {
            string searchPattern = string.Format(_bareFileFormat, "*");
            FileInfo[] files = _writingFileInfo.Directory.GetFiles(searchPattern,
                SearchOption.TopDirectoryOnly);
            Array.Sort<FileInfo>(files, new InverseComparer<FileInfo>(LastWrittenDateComparer.Instance));
            //files.QuickSort(LastWrittenDateComparer.Instance, false);
            foreach (FileInfo info in files)
            {
                _previousFileNamesQueue.Enqueue(info.FullName);
            }
        }

        private bool tryRollingWritingFile(bool createEmptyFile, bool checkForExists)
        {
            bool shouldRename = true;
            if (checkForExists)
            {
                _writingFileInfo.Refresh();
                if (_writingFileInfo.Exists)
                {
                    if (_writingFileInfo.Length <= newFileSizeThreshold)
                    {
                        shouldRename = false;
                    }
                }
                else
                {
                    shouldRename = false;
                }
            }
            if (shouldRename)
            {
                string s = GlobalSettings.Instance.CurrentDateTime.ToString("yyyyMMdd HHmmssff",
                    CultureInfo.InvariantCulture);
                string newFileName = string.Format(_fileFormat, s);
                bool isRenamed = IOHelper.MoveFile(_writingFileInfo.FullName, newFileName,
                    false);
                if (isRenamed)
                {
                    if (ZipOption != ZipOptions.None)
                    {
                        Task.Factory.StartNew(state => { zipFile(newFileName); }, null);
                    }
                    _previousFileNamesQueue.Enqueue(newFileName);
                    if (createEmptyFile)
                    {
                        createNewFile();
                    }
                    return true;
                }
            }
            return false;
        }

        private void _previousFileNamesQueue_OnQueueFull(object sender,
            SizedQueueOnFullEventArgs<string> e)
        {
            IOHelper.DeleteFile(e.Item, false);
        }

        #endregion Methods
    }
}